package ui;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.LayoutManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import computation.Statistics;

import primatives.PointND;

import triangulation.SimpleTriangulation;
import triangulation.SimpleTriangulation.SCALING_MODE;
import triangulation.SimpleTriangulation.SELECTION_MODE;
import util.DataGenerator;
import util.GeoVertex;

public class OptionsPanel extends JPanel
{
	/**
	 * 
	 */
	private static final long serialVersionUID = -2187843605876251281L;
	private JTextField txtStrikeFrom;
	private JTextField txtStrikeTo;
	private JTextField txtDipFrom;
	private JTextField txtDipTo;
	
	// laser camera head orientation
	private JTextField txtLaserHeadS;
	private JTextField txtLaserHeadD;
	
	private JRadioButton btnDisplayStrike;
	private JRadioButton btnDisplayDip;
	private JCheckBox cbUsePerVertex; // should we display strike n dip using per vertex or per triangle?
	private JCheckBox cbUsePerTriangle; 
	
	private SimpleTriangulation mesh;
	
	private JTextField txtRougnessFrom;
	private JTextField txtRougnessTo;
	
	private JTextField txtRoughnessRatioFrom;
	private JTextField txtRoughnessRatioTo;
	
	// defines the query box
	private JTextField txtWindowQueryX;
	private JTextField txtWindowQueryY;
	private JTextField txtWindowQueryW;
	private JTextField txtWindowQueryH;
	
	// roughness map computation fields
	private JTextField txtScaleParameter;
	private JTextField txtSmoothingIter;
	private JCheckBox cbShowCurvatureAnisotropic_ORI; // kmax value on original mesh
	private JCheckBox cbShowCurvatureAvg_ORI; // kav value on original mesh
	private JCheckBox cbShowCurvatureAnisotropic_SMO; // kmax value on smoothed mesh
	private JCheckBox cbShowCurvatureAvg_SMO; // kav value on smoothed mesh
	private JCheckBox cbShowRoughnessMap;
	
	// principle curvature computation fields
	private JCheckBox cbShowCurvatureMean;
	private JCheckBox cbShowCurvatureGaussian;
	
	// n-ring curvature fields
	private JTextField txtNRing;
	
	// color scaling variables (only applies wavelength light coloring)
	private JCheckBox cbColorScale_NONE;
	private JCheckBox cbColorScale_LOG;
	private JCheckBox cbColorScale_SQRT;
	
	
	// statistics panel variables
	private JCheckBox statsAverage;
	private JCheckBox statsRootMeanSquare;
	private JCheckBox statsTenpoint;
	private JCheckBox statsSkewness;
	private JCheckBox statsKurtosis;
	private JTextArea txtStatsArea;
	private JButton btnComputeStats;
	
	// surface generation panel variables
	private JPanel generationOptionsPanel;
	private JPanel surfaceGeneration;
	private JList surfaceTypes;
	private String[] strSurfaceTypes = {"Flat", "Spikes", "Semi-Flat", "Random", "Bedding", "Cone"};
	private final int BASE_ELEVATION = -500;
	private JSlider sliderSurfaceWidth;
	private JSlider sliderSurfaceHeight;
	private JTextField flatElevation; // essentially controls how close and far the surface is wrt the viewport
	private JTextField spikedElevation; // controls the height of the spike
	private JTextField spikedFlatElevation; // controls the height of flat area
	private JTextField semiFlatElevation; // controls the initial randomness
	private JTextField semiFlatToleranceElevation; // limits the randomness to be within +/- this value
	private JTextField randomElevation; // competely random from 0 to this value
	private JTextField coneDegrees; // increments for the cone
	private JTextField coneRadius; // radius of the cone
	private JTextField coneHeight; // height of the cone
	
	// int layers, int w, double min, double max, double layer_spacing, double layer_depth_spacing, double surface_max, double surface_min
	private JTextField beddingLayers;
	private JTextField beddingWidth;
	private JTextField layerHeightMin;
	private JTextField layerHeightMax;
	private JTextField layerSpacing;
	private JTextField layerDepth;
	private JTextField layerSurfaceMax;
	private JTextField layerSurfaceMin;

	// surface overlay panel variables
	private JList overlayTypes;
	private JPanel overlayPanel;
	private String[] strOverlayTypes = {"Strike & Dip", "Peak, Valley & Saddle", "Roughness v1", "Range Query", "Roughness Map", "Principle Curvature", "N-Ring Curvature Maps"};
	
	// surface smoothing panel
	private JList smoothingAlgorithms;
	private JPanel smoothingPanel;
	private String[] strOperationTypes = {"Smoothing: Geometric Laplacian", "Smoothing: Adaptive Smoothing", "Taubin"};
	private JSlider sliderSmoothnessLevel; // controls which level of smoothness mesh to display
	private JTextField txtLaplacianIterations;
	
	private JTextField txtAdaptiveIterations; // adaptive
	private JTextField txtAdaptiveScale;
	private JTextField txtTaubinIterations;
	
	// general options panel
	private JCheckBox cbLightSwitch;
	private JCheckBox cbLightsPerTriangle;
	private JCheckBox cbLightsPerVertex;
//	private JCheckBox cbBackFaceCulling;
	private JRadioButton rRenderPolygons;
	private JRadioButton rRenderLines;
	private JRadioButton rRenderPoints;
	private JButton btnShowTriangulation; // show 2d version of triangulation
	private JTextField txtMeshScale; // scaling the mesh
	private JCheckBox cbDisplayNormals; // option to enable/disable the rendering of normal vectors from the center of the triangle surfaces
	private JButton btnShowStrikeDip2D; // show a 2d circle with strike as the angle around the circle and the dip as the magnitude
	
	// TODO: all the text fields create a new numeric key adapter, perhaps create just one and get all the text fields to use it?
	public class NumericKeyAdapter extends KeyAdapter
	{
		public void keyTyped(KeyEvent e)
		{
			// eat any characters that are not digits (and some special characters like backspace)
			char c = e.getKeyChar();
            if (!((c==KeyEvent.VK_BACK_SPACE) || (c==KeyEvent.VK_DELETE) ||  (c== KeyEvent.VK_ENTER)  || (c == KeyEvent.VK_TAB) ||
            		(c==KeyEvent.VK_DECIMAL) || (c==KeyEvent.VK_PERIOD) || (c==KeyEvent.VK_MINUS) || (Character.isDigit(c)))) e.consume();
		}
	}
	
	// used for quick testing of functions
	protected JPanel createTestPanel()
	{
		JPanel panel = new JPanel();
		
		JButton btnTest = new JButton("Test");
		btnTest.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent arg0)
			{
				if (mesh != null)
				{
					mesh.computePrincipleCurvatures_Taubin();
				}
				else
				{
					
				}
			}
		});
		
		panel.add(btnTest);
		
		return panel;
	}
	
	public OptionsPanel()
	{
		super();
		mesh = null;
		
		JTabbedPane tabs = new JTabbedPane();
		tabs.setBorder(BorderFactory.createTitledBorder("Options:"));
		tabs.setSize(UIConst.APP_WIDTH, UIConst.OPTIONS_PANEL_OFFSET);
//		tabs.addTab("TEST", createTestPanel());
		tabs.addTab("General", createGeneralOptionsPanel());
		tabs.addTab("Statistics", createSurfaceStatisticsPanel());
		tabs.addTab("Generation", createSurfaceGenerationPanel());
		tabs.addTab("Overlays", createSurfaceOverlayPanel());
		tabs.addTab("Mesh Operations", createMeshOperationsPanel());
		
		add(tabs);
	}

	protected JPanel createSurfaceStatisticsPanel()
	{
		JPanel panel = new JPanel(new GridBagLayout());
		GridBagConstraints c = new GridBagConstraints();
		c.fill = GridBagConstraints.BOTH;
		
		JPanel statsSelectionPanel = new JPanel(new GridLayout(5, 1));
		statsAverage = new JCheckBox("Average", true);
		statsRootMeanSquare = new JCheckBox("Root Mean Square", true);
		statsTenpoint = new JCheckBox("Tenpoint", true);
		statsSkewness = new JCheckBox("Skewness", true);
		statsKurtosis = new JCheckBox("Kurtosis", true);
		
		statsSelectionPanel.add(statsAverage);
		statsSelectionPanel.add(statsRootMeanSquare);
		statsSelectionPanel.add(statsTenpoint);
		statsSelectionPanel.add(statsSkewness);
		statsSelectionPanel.add(statsKurtosis);
		
		JPanel statsPanel = new JPanel(new GridBagLayout());
		GridBagConstraints c2 = new GridBagConstraints();
		c2.fill = GridBagConstraints.BOTH;
		txtStatsArea = new JTextArea();
		txtStatsArea.setEditable(false);
		btnComputeStats = new JButton("Compute statistics.");
		btnComputeStats.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				String statsSummary = "=== Summary ===\n";
				
				// TODO: ideally one should be able to compute the stats for a subset of vertices
				if (statsAverage.isSelected()) statsSummary += ("Average: " +Statistics.computeAverageSurfaceHeight(mesh.mapping.vertices) + "\n");
				if (statsRootMeanSquare.isSelected()) statsSummary += ("Root Mean Square: " +Statistics.computeRMSSurfaceHeight(mesh.mapping.vertices) + "\n");
				if (statsTenpoint.isSelected()) statsSummary += ("Tenpoint: " +Statistics.computeTenpointHeight(mesh.mapping.vertices) + "\n");
				if (statsSkewness.isSelected()) statsSummary += ("Skewness: " +Statistics.computeSkewness(mesh.mapping.vertices) + "\n");
				if (statsKurtosis.isSelected()) statsSummary += ("Kurtosis: " +Statistics.computeKurtosis(mesh.mapping.vertices) + "\n");
				
				txtStatsArea.setText(statsSummary);
			}
		});

		// some cosmetics such as a border, still need to add scroll bar to txtStatsArea
		JPanel txtAreaPanel = new JPanel(new BorderLayout());
		txtAreaPanel.setBorder(BorderFactory.createEtchedBorder());
		txtAreaPanel.add(txtStatsArea, BorderLayout.CENTER);
		
		c2.gridx = 0;
		c2.gridy = 0;
		c2.weightx = 1.0;
		c2.weighty = 0.75;
		statsPanel.add(txtAreaPanel, c2);
		
		c2.gridx = 0;
		c2.gridy = 1;
		c2.weighty = 0.25;
		c2.fill = GridBagConstraints.NONE;
		statsPanel.add(btnComputeStats, c2);
		
		c.gridx = 0;
		c.gridy = 0;
		c.weightx = 0.25;
		c.anchor = GridBagConstraints.WEST;
		panel.add(statsSelectionPanel);
		
		c.gridx = 1;
		c.gridy = 0;
		c.weightx = 0.75;
		c.anchor = GridBagConstraints.CENTER;
		panel.add(statsPanel, c);
		return panel;
	}
	
	protected JPanel createSurfaceGenerationPanel()
	{
		JPanel panel = new JPanel(new GridBagLayout());
		GridBagConstraints c = new GridBagConstraints();
		c.fill = GridBagConstraints.BOTH; // natural height, maximum width
		surfaceGeneration = new JPanel(new CardLayout()); 		// panel that holds all the different surface generation panels
		
		surfaceTypes = new JList(strSurfaceTypes);
		surfaceTypes.addListSelectionListener(new ListSelectionListener() {
			
			@Override
			public void valueChanged(ListSelectionEvent e)
			{
				String type = (String) surfaceTypes.getSelectedValue();
				if (type != null)
				{
					CardLayout c = (CardLayout)surfaceGeneration.getLayout();
					c.show(surfaceGeneration, type);
				}
			}
		});
		
		JScrollPane surfaceListPane = new JScrollPane(surfaceTypes);
		surfaceTypes.setSelectedIndex(0);
		
		// contains panel for options per surface generation and a button to actually generate the surface
		generationOptionsPanel = new JPanel(new BorderLayout());
		sliderSurfaceWidth = new JSlider(10, 200);
		sliderSurfaceWidth.setValue(100);
		sliderSurfaceWidth.setName("Width");
		sliderSurfaceHeight = new JSlider(10, 200);
		sliderSurfaceHeight.setValue(100);
		
		// surface dimensions panel for all surfaces
		JPanel surfaceDimensions = new JPanel();
		JPanel surfaceWidth = new JPanel();
		JPanel surfaceHeight = new JPanel();
		surfaceWidth.add(new JLabel("Width: "));
		surfaceWidth.add(sliderSurfaceWidth);
		surfaceHeight.add(new JLabel("Height: "));
		surfaceHeight.add(sliderSurfaceHeight);
		surfaceDimensions.add(surfaceWidth);
		surfaceDimensions.add(surfaceHeight);
		
		// flat surface generation panel
		JPanel flatSurface = new JPanel();
		flatElevation = new JTextField(""+BASE_ELEVATION, 5);
		flatElevation.addKeyListener(new NumericKeyAdapter());
		flatSurface.add(new JLabel("Elevation: "));
		flatSurface.add(flatElevation);
		
		// spiked surface generation panel
		JPanel spikedSurface = new JPanel(new GridLayout(2, 2));
		spikedElevation = new JTextField(""+(BASE_ELEVATION+5), 5);
		spikedElevation.addKeyListener(new NumericKeyAdapter());
		spikedFlatElevation = new JTextField(""+BASE_ELEVATION, 5);
		spikedFlatElevation.addKeyListener(new NumericKeyAdapter());
		spikedSurface.add(new JLabel("Spike Elevation: "));
		spikedSurface.add(spikedElevation);
		spikedSurface.add(new JLabel("Flat Elevation:"));
		spikedSurface.add(spikedFlatElevation);
		
		// semi flat surface generation panel
		JPanel semiFlatSurface = new JPanel(new GridLayout(2, 2));
		semiFlatElevation = new JTextField(""+BASE_ELEVATION, 5);
		semiFlatElevation.addKeyListener(new NumericKeyAdapter());
		semiFlatToleranceElevation = new JTextField("5", 5);
		semiFlatToleranceElevation.addKeyListener(new NumericKeyAdapter());
		semiFlatSurface.add(new JLabel("Elevation: "));
		semiFlatSurface.add(semiFlatElevation);
		semiFlatSurface.add(new JLabel("Tolerance: "));
		semiFlatSurface.add(semiFlatToleranceElevation);
		
		
		// random surface generation panel
		JPanel randomSurface = new JPanel();
		randomElevation = new JTextField(""+BASE_ELEVATION, 5);
		randomElevation.addKeyListener(new NumericKeyAdapter());
		randomSurface.add(new JLabel("Maximum Elevation: "));
		randomSurface.add(randomElevation);
		
		
		// bedding surface generation panel
		JPanel beddingSurface = new JPanel(new GridLayout(4, 4));
		beddingLayers = new JTextField("6", 4);
		beddingWidth = new JTextField("100", 4);
		layerHeightMin = new JTextField("10", 4);
		layerHeightMax = new JTextField("20", 4);
		layerSpacing = new JTextField("1", 4);
		layerDepth = new JTextField("10", 4);
		layerSurfaceMax = new JTextField("-500", 4);
		layerSurfaceMin = new JTextField("-499", 4);
		
		beddingLayers.addKeyListener(new NumericKeyAdapter());
		beddingWidth.addKeyListener(new NumericKeyAdapter());
		layerHeightMin.addKeyListener(new NumericKeyAdapter());
		layerHeightMax.addKeyListener(new NumericKeyAdapter());
		layerSpacing.addKeyListener(new NumericKeyAdapter());
		layerDepth.addKeyListener(new NumericKeyAdapter());
		layerSurfaceMax.addKeyListener(new NumericKeyAdapter());
		layerSurfaceMin.addKeyListener(new NumericKeyAdapter());
		
		beddingSurface.add(new JLabel("# of Layers"));
		beddingSurface.add(beddingLayers);
		beddingSurface.add(new JLabel("Bedding Width"));
		beddingSurface.add(beddingWidth);
		beddingSurface.add(new JLabel("Layer Height Min"));
		beddingSurface.add(layerHeightMin);
		beddingSurface.add(new JLabel("Layer Height Max"));
		beddingSurface.add(layerHeightMax);

		beddingSurface.add(new JLabel("Layer Spacing"));
		beddingSurface.add(layerSpacing);
		beddingSurface.add(new JLabel("Layer Depth"));
		beddingSurface.add(layerDepth);
		beddingSurface.add(new JLabel("Surface Height Max"));
		beddingSurface.add(layerSurfaceMax);
		beddingSurface.add(new JLabel("Surface Height Min"));
		beddingSurface.add(layerSurfaceMin);
		
		JPanel coneSurface = new JPanel(new GridLayout(3, 2));
		coneRadius = new JTextField("25.0", 5);
		coneHeight = new JTextField("25.0", 5);
		coneDegrees = new JTextField("3.6", 5);
		
		coneRadius.addKeyListener(new NumericKeyAdapter());
		coneHeight.addKeyListener(new NumericKeyAdapter());
		coneDegrees.addKeyListener(new NumericKeyAdapter());
		
		coneSurface.add(new JLabel("Radius: "));
		coneSurface.add(coneRadius);
		coneSurface.add(new JLabel("Height: "));
		coneSurface.add(coneHeight);
		coneSurface.add(new JLabel("Degree Increment: "));
		coneSurface.add(coneDegrees);
		
		surfaceGeneration.add(flatSurface, strSurfaceTypes[0]);
		surfaceGeneration.add(spikedSurface, strSurfaceTypes[1]);
		surfaceGeneration.add(semiFlatSurface, strSurfaceTypes[2]);
		surfaceGeneration.add(randomSurface, strSurfaceTypes[3]);
		surfaceGeneration.add(beddingSurface, strSurfaceTypes[4]);
		surfaceGeneration.add(coneSurface, strSurfaceTypes[5]);
		
		JButton generateSurface = new JButton("Generate Surface");
		generateSurface.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent e)
			{
				int type = surfaceTypes.getSelectedIndex();
				if (type != -1)
				{
					int width = sliderSurfaceWidth.getValue();
					int height = sliderSurfaceHeight.getValue();
					ArrayList<PointND> pointcloud = null;
					switch(type)
					{
						case 0:
							String f_Elevation = flatElevation.getText();
							double f_E = Double.parseDouble(f_Elevation);
							pointcloud = DataGenerator.generatePerfectFlat(width, height, f_E);
							break;
						case 1:
							String s_Elevation = spikedElevation.getText();
							String sf_Elevation = spikedFlatElevation.getText();
							double s_E = Double.parseDouble(s_Elevation);
							double sf_E = Double.parseDouble(sf_Elevation);
							pointcloud = DataGenerator.generateSpikes(width, height, s_E, sf_E);
							break;
						case 2:
							String sem_Elevation = semiFlatElevation.getText();
							String sem_Tolarance = semiFlatToleranceElevation.getText();
							double sem_E = Double.parseDouble(sem_Elevation);
							double sem_T = Double.parseDouble(sem_Tolarance);
							pointcloud = DataGenerator.generateSemiFlat(width, height, sem_E, sem_T);
							break;
						case 3:
							String r_Elevation = randomElevation.getText();
							double r_E = Double.parseDouble(r_Elevation);
							pointcloud = DataGenerator.generateRandom(width, height, r_E);
							break;
						case 4:
							int numLayers = Integer.parseInt(beddingLayers.getText());
							int wLayer = Integer.parseInt(beddingWidth.getText());
							double layerHMax = Double.parseDouble(layerHeightMax.getText());
							double layerHMin = Double.parseDouble(layerHeightMin.getText());
							double spacing = Double.parseDouble(layerSpacing.getText());
							double depth = Double.parseDouble(layerDepth.getText());
							double surfaceHMax = Double.parseDouble(layerSurfaceMax.getText());
							double surfaceHMin = Double.parseDouble(layerSurfaceMin.getText());
							pointcloud = DataGenerator.generateBedding(numLayers, wLayer, layerHMin, layerHMax, spacing, depth, surfaceHMax, surfaceHMin);
							break;
						case 5:
							double radius = Double.parseDouble(coneRadius.getText());
							double cone_height = Double.parseDouble(coneHeight.getText());
							double degree_increments = Double.parseDouble(coneDegrees.getText());
							pointcloud = DataGenerator.generateCone(radius, cone_height, degree_increments);
							break;
						default:
							break;
					}
					if (pointcloud != null)
					{
						mesh.smoothMesh_Reset(); // reset smoothness indices
						mesh.triangulate(pointcloud);
					}
				}

			}
		});

		
		generationOptionsPanel.add(BorderLayout.NORTH, surfaceDimensions);
		generationOptionsPanel.add(BorderLayout.CENTER, surfaceGeneration);
		generationOptionsPanel.add(BorderLayout.SOUTH, generateSurface);
		
		c.gridx = 0;
		c.gridy = 0;
		c.anchor = GridBagConstraints.WEST;
		c.weightx = 0.25;
		panel.add(surfaceListPane, c);
		
		c.gridx = 1;
		c.gridy = 0;
		c.anchor = GridBagConstraints.CENTER;
		c.weightx = 0.75;
		panel.add(generationOptionsPanel, c);
		
		return panel;
	}

	protected JPanel createSurfaceOverlayPanel()
	{
		JPanel panel = new JPanel(new GridBagLayout());
		GridBagConstraints c = new GridBagConstraints();
		c.fill = GridBagConstraints.BOTH;
		overlayPanel = new JPanel(new CardLayout());

		overlayTypes = new JList(strOverlayTypes);
		overlayTypes.addListSelectionListener(new ListSelectionListener() {
			
			@Override
			public void valueChanged(ListSelectionEvent e)
			{
				String type = (String) overlayTypes.getSelectedValue();
				if (type != null)
				{
					CardLayout c = (CardLayout)overlayPanel.getLayout();
					c.show(overlayPanel, type);
				}
			}
		});
		
		JScrollPane overlayListPane = new JScrollPane(overlayTypes);
		overlayTypes.setSelectedIndex(0);

		
		overlayPanel.add(createStrikeDipSearchPanel(), strOverlayTypes[0]);
		overlayPanel.add(createPeakValleySaddleSearchPanel(), strOverlayTypes[1]);
		overlayPanel.add(createRoughnessSearchPanel(), strOverlayTypes[2]);
		overlayPanel.add(createRangeQueryPanel(), strOverlayTypes[3]);
		overlayPanel.add(createRoughnessMapComputationPanel(), strOverlayTypes[4]);
		overlayPanel.add(createPrincipleCurvatureComputationPanel(), strOverlayTypes[5]);
		overlayPanel.add(createRingCurvatureComputationPanel(), strOverlayTypes[6]);
		
		c.gridx = 0;
		c.gridy = 0;
		c.weightx = 0.25;
		c.anchor = GridBagConstraints.WEST;
		panel.add(overlayListPane, c);
		
		c.gridx = 1;
		c.gridy = 0;
		c.weightx = 0.75;
		c.anchor = GridBagConstraints.EAST;
		panel.add(overlayPanel, c);
		
		return panel;
	}

	// SURFACE OVERLAY PANELS
	protected JPanel createStrikeDipSearchPanel()
	{
		JPanel panel = new JPanel(new GridBagLayout());
		GridBagConstraints c = new GridBagConstraints();
		c.fill = GridBagConstraints.NONE;
		
		txtStrikeFrom = new JTextField("0.0", 5);
		txtStrikeTo = new JTextField("360.0", 5);
		
		txtDipFrom = new JTextField("0.0", 5);
		txtDipTo = new JTextField("90.0", 5);
		
		// negatives on both parameters means we strike dip computation straight from the normal
		txtLaserHeadS = new JTextField("-1.0", 5);
		txtLaserHeadD = new JTextField("-1.0", 5);
		
		txtStrikeFrom.addKeyListener(new NumericKeyAdapter());
		txtStrikeTo.addKeyListener(new NumericKeyAdapter());
		txtDipFrom.addKeyListener(new NumericKeyAdapter());
		txtDipTo.addKeyListener(new NumericKeyAdapter());
		txtLaserHeadS.addKeyListener(new NumericKeyAdapter());
		txtLaserHeadD.addKeyListener(new NumericKeyAdapter());
		
		JButton btnSearchStrike = new JButton("Search");
		btnSearchStrike.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent arg0)
			{
				if (mesh != null)
				{
					String strStrike = txtStrikeFrom.getText();
					String strStrikeRng = txtStrikeTo.getText();
					
					double strikeDeg = Double.valueOf(strStrike);
					double strikeRng = Double.valueOf(strStrikeRng);
					boolean bStrikeValid = strikeDeg >= 0.0 && strikeDeg <= 360.0;
					boolean bStrikeRngValid = strikeRng >= 0.0;
					
					if (bStrikeValid && bStrikeRngValid)
					{
						mesh.searchStrikeRange(strikeDeg, strikeRng);
						mesh.setMode(SELECTION_MODE.STRIKE_DIP_SEARCH);
					}
				}
				else
				{
					
				}

			}
		});
		
		JButton btnSearchDip = new JButton("Search");
		btnSearchDip.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent arg0)
			{
				if (mesh != null)
				{
					String strDip = txtDipFrom.getText();
					String strDipRng = txtDipTo.getText();
					
					double dipDeg = Double.valueOf(strDip);
					double dipRng = Double.valueOf(strDipRng);
					
					boolean bDipValid = dipDeg >= 0.0 && dipDeg <= 90.0;
					boolean bDipRngValid = dipRng >= 0.0;
					
					if (bDipValid && bDipRngValid)
					{
						mesh.searchDipRange(dipDeg, dipRng);
						mesh.setMode(SELECTION_MODE.STRIKE_DIP_SEARCH);
					}
				}
				else
				{
					
				}

			}
		});
		
		JPanel strikeSearch = new JPanel();
		strikeSearch.setBorder(BorderFactory.createTitledBorder("Strike"));
		strikeSearch.add(new JLabel("From"));
		strikeSearch.add(txtStrikeFrom);
		strikeSearch.add(new JLabel("To"));
		strikeSearch.add(txtStrikeTo);
		strikeSearch.add(btnSearchStrike);
//		panel.add(strikeSearch);
		
		JPanel dipSearch = new JPanel();
		dipSearch.setBorder(BorderFactory.createTitledBorder("Dip"));
		dipSearch.add(new JLabel("From"));
		dipSearch.add(txtDipFrom);
		dipSearch.add(new JLabel("To"));
		dipSearch.add(txtDipTo);
		dipSearch.add(btnSearchDip);
//		panel.add(dipSearch);
		
		JPanel laserHead = new JPanel();
		laserHead.setBorder(BorderFactory.createTitledBorder("Laser Head Orientation"));
		laserHead.add(new JLabel("Strike"));
		laserHead.add(txtLaserHeadS);
		laserHead.add(new JLabel("Dip"));
		laserHead.add(txtLaserHeadD);
//		panel.add(laserHead);

		btnDisplayStrike = new JRadioButton("Display Strike");
		btnDisplayDip = new JRadioButton("Display Dip");
		
		btnDisplayStrike.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent arg0)
			{
				if (mesh != null)
				{
					boolean bShowStrike = btnDisplayStrike.isSelected();
					boolean bShowDip = btnDisplayDip.isSelected();
					
					if (bShowStrike && bShowDip) mesh.setMode(SELECTION_MODE.STRIKE_DIP);
					else if (bShowStrike && !bShowDip) mesh.setMode(SELECTION_MODE.STRIKE_ONLY);
					else if (!bShowStrike && bShowDip) mesh.setMode(SELECTION_MODE.DIP_ONLY);
					else mesh.setMode(SELECTION_MODE.NONE);
					mesh.updateColors = true;
				}
				else
				{
					
				}

			}
		});

		btnDisplayDip.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent arg0)
			{
				if (mesh != null)
				{
					boolean bShowStrike = btnDisplayStrike.isSelected();
					boolean bShowDip = btnDisplayDip.isSelected();
					
					if (bShowStrike && bShowDip) mesh.setMode(SELECTION_MODE.STRIKE_DIP);
					else if (bShowStrike && !bShowDip) mesh.setMode(SELECTION_MODE.STRIKE_ONLY);
					else if (!bShowStrike && bShowDip) mesh.setMode(SELECTION_MODE.DIP_ONLY);
					else mesh.setMode(SELECTION_MODE.NONE);
					mesh.updateColors = true;
				}
				else
				{
					
				}

			}
		});
		
		JButton computeStrikeDip = new JButton("Compute Strike N Dip");
		computeStrikeDip.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				String strLaserS = txtLaserHeadS.getText();
				String strLaserD = txtLaserHeadD.getText();
				
				double dLaserS = Double.valueOf(strLaserS);
				double dLaserD = Double.valueOf(strLaserD);
				
				if (mesh != null)
				{
					mesh.dLaserStrike = dLaserS;
					mesh.dLaserDip = dLaserD;
					mesh.computeStrikeDip_All();
					mesh.updateColors = true;
				}
				else
				{
					
				}
			}
		});
		
		cbUsePerVertex = new JCheckBox("Per Vertex", true);
		cbUsePerTriangle = new JCheckBox("Per Triangle");
		
		ButtonGroup group = new ButtonGroup();
		group.add(cbUsePerVertex);
		group.add(cbUsePerTriangle);
		
		cbUsePerVertex.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				mesh.perVertexStrikeDip = true;
				mesh.perTriangleStrikeDip = false;
				mesh.updateColors = true;
			}
		});
		
		cbUsePerTriangle.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				mesh.perVertexStrikeDip = false;
				mesh.perTriangleStrikeDip = true ;
				mesh.updateColors = true;
			}
		});
		
		
		JButton btnSearchStrikeDip = new JButton("Search Strike N' Dip");
		btnSearchStrikeDip.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent arg0)
			{
				if (mesh != null)
				{
					String strStrikeF = txtStrikeFrom.getText();
					String strStrikeT = txtStrikeTo.getText();
					
					String strDipF = txtDipFrom.getText();
					String strDipT = txtDipTo.getText();
					
					double dipDegF = Double.valueOf(strDipF);
					double dipDegT = Double.valueOf(strDipT);
					
					double strikeDegF = Double.valueOf(strStrikeF);
					double strikeDegT = Double.valueOf(strStrikeT);
					
					boolean bDipValid = dipDegF >= 0.0 && dipDegT <= 90.0;
					boolean bStrikeValid = strikeDegF >= 0.0 && strikeDegT <= 360.0;
					
					if (bStrikeValid && bDipValid)
					{
						mesh.searchDipRange(dipDegF, dipDegT);
						mesh.searchStrikeDipRange(strikeDegF, strikeDegT, dipDegF, dipDegT);
						mesh.setMode(SELECTION_MODE.STRIKE_DIP_SEARCH);
					}
				}
				else
				{
				}
			}
		});
		
		JPanel displayOptions = new JPanel(new GridLayout(2, 2));
		displayOptions.add(cbUsePerVertex);
		displayOptions.add(cbUsePerTriangle);
		displayOptions.add(btnDisplayStrike);
		displayOptions.add(btnDisplayDip);
//		panel.add(displayOptions);
		
		JPanel extraButtonsPanel = new JPanel(new GridLayout(2,1));
		extraButtonsPanel.add(computeStrikeDip);
		extraButtonsPanel.add(btnSearchStrikeDip);
		
		c.gridx = 0;
		c.gridy = 0;
		c.anchor = GridBagConstraints.WEST;
		panel.add(laserHead, c);

		c.gridx = 1;
		c.gridy = 0;
		panel.add(strikeSearch, c);
		
		c.gridx = 1;
		c.gridy = 1;
		panel.add(dipSearch, c);
	
		c.gridx = 2;
		c.gridy = 0;
		c.anchor = GridBagConstraints.EAST;
		panel.add(displayOptions, c);
		
		c.gridx = 2;
		c.gridy = 1;
		panel.add(extraButtonsPanel, c);
		
		return panel;
	}
	
	protected JPanel createPeakValleySaddleSearchPanel()
	{
		JPanel panel = new JPanel();
		txtRoughnessRatioFrom = new JTextField("0.5", 10);
		txtRoughnessRatioFrom.addKeyListener(new NumericKeyAdapter());
		
		txtRoughnessRatioTo = new JTextField("1.0", 10);
		txtRoughnessRatioTo.addKeyListener(new NumericKeyAdapter());

		JButton btnSearch = new JButton("Search");
		btnSearch.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent arg0)
			{
				if (mesh != null)
				{
					String strRatioFrom = txtRoughnessRatioFrom.getText();
					String strRatioTo = txtRoughnessRatioTo.getText();

					
					double ratioFrom = Double.valueOf(strRatioFrom);
					double ratioTo = Double.valueOf(strRatioTo);
					
					boolean bFromValid = ratioFrom >= 0.0 && ratioFrom <= ratioTo;
					boolean bToValid = ratioTo >= ratioFrom && ratioTo <= 1.0;
					
					if (bFromValid && bToValid)
					{
						mesh.searchRoughnessRatio(ratioFrom, ratioTo);
						mesh.setMode(SELECTION_MODE.PEAK_VALLEY_SADDLE);
					}
				}
				else
				{
					
				}
			}
		});
		
		panel.add(new JLabel("From"));
		panel.add(txtRoughnessRatioFrom);
		panel.add(new JLabel("To"));
		panel.add(txtRoughnessRatioTo);
		panel.add(btnSearch);
		
		return panel;
	}
	
	protected JPanel createRoughnessSearchPanel()
	{
		JPanel panel = new JPanel();
		txtRougnessFrom = new JTextField("0.0", 10);
		txtRougnessFrom.addKeyListener(new NumericKeyAdapter());
		
		txtRougnessTo = new JTextField("100.0", 10);
		txtRougnessTo.addKeyListener(new NumericKeyAdapter());

		JButton btnSearch = new JButton("Search");
		btnSearch.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent arg0)
			{
				if (mesh != null)
				{
					String strFrom = txtRougnessFrom.getText();
					String strTo = txtRougnessTo.getText();

					double from = Double.valueOf(strFrom);
					double to = Double.valueOf(strTo);
					
					boolean bFromValid = from >= 0.0 && from <= to;
					boolean bToValid = to >= from && to <= 100.0;

					if (bFromValid && bToValid)
					{
						mesh.searchRoughness(from, to);
						mesh.setMode(SELECTION_MODE.ROUGHNESS);
						mesh.updateColors = true;
					}
				}
				else
				{
					
				}

			}
		});
		
		panel.add(new JLabel("From"));
		panel.add(txtRougnessFrom);
		panel.add(new JLabel("To"));
		panel.add(txtRougnessTo);
		panel.add(btnSearch);
		
		return panel;
	}
	
	protected JPanel createRangeQueryPanel()
	{
		JPanel panel = new JPanel();
		txtWindowQueryX = new JTextField("0", 10);
		txtWindowQueryY = new JTextField("0", 10);
		txtWindowQueryW = new JTextField("10", 10);
		txtWindowQueryH = new JTextField("5", 10);
		
		txtWindowQueryX.addKeyListener(new NumericKeyAdapter());
		txtWindowQueryY.addKeyListener(new NumericKeyAdapter());
		txtWindowQueryW.addKeyListener(new NumericKeyAdapter());
		txtWindowQueryH.addKeyListener(new NumericKeyAdapter());

		JButton btnSearch = new JButton("Search");
		btnSearch.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent arg0)
			{
				if (mesh != null)
				{
					String strX = txtWindowQueryX.getText();
					String strY = txtWindowQueryY.getText();
					String strW = txtWindowQueryW.getText();
					String strH = txtWindowQueryH.getText();

					double X = Double.valueOf(strX);
					double Y = Double.valueOf(strY);
					double W = Double.valueOf(strW);
					double H = Double.valueOf(strH);
					
					mesh.searchRange(X, Y, W, H);
					mesh.setMode(SELECTION_MODE.RANGE_QUERY);
				}
				else
				{
					
				}

			}
		});
		
		panel.add(new JLabel("X"));
		panel.add(txtWindowQueryX);
		panel.add(new JLabel("Y"));
		panel.add(txtWindowQueryY);
		panel.add(new JLabel("W"));
		panel.add(txtWindowQueryW);
		panel.add(new JLabel("H"));
		panel.add(txtWindowQueryH);
		panel.add(btnSearch);
		
		return panel;
	}
	
	protected JPanel createRoughnessMapComputationPanel()
	{
		JPanel panel = new JPanel(new GridBagLayout());
		GridBagConstraints c = new GridBagConstraints();
		c.fill = GridBagConstraints.NONE;
		
		txtScaleParameter = new JTextField("1", 5); // scale parameter is in percents
		txtSmoothingIter = new JTextField("5", 5); // number of iterations for the adaptive smoothing
		
		txtScaleParameter.addKeyListener(new NumericKeyAdapter());
		txtSmoothingIter.addKeyListener(new NumericKeyAdapter());

		
		JPanel displayOptions = new JPanel(new GridLayout(3, 1));
		cbShowRoughnessMap = new JCheckBox("Roughness Map", true);
		cbShowCurvatureAvg_ORI = new JCheckBox("Average Curvature (Original)");
		cbShowCurvatureAnisotropic_ORI = new JCheckBox("Maximum Curvature (Original)");
		cbShowCurvatureAvg_SMO = new JCheckBox("Average Curvature (Smoothed)");
		cbShowCurvatureAnisotropic_SMO = new JCheckBox("Maximum Curvature (Smoothed)");
		
		ButtonGroup displayGroup = new ButtonGroup();
		displayGroup.add(cbShowRoughnessMap);
		displayGroup.add(cbShowCurvatureAvg_ORI);
		displayGroup.add(cbShowCurvatureAnisotropic_ORI);
		displayGroup.add(cbShowCurvatureAvg_SMO);
		displayGroup.add(cbShowCurvatureAnisotropic_SMO);
		displayOptions.add(cbShowRoughnessMap);
		displayOptions.add(new JLabel());
		displayOptions.add(cbShowCurvatureAvg_ORI);
		displayOptions.add(cbShowCurvatureAnisotropic_ORI);
		displayOptions.add(cbShowCurvatureAvg_SMO);
		displayOptions.add(cbShowCurvatureAnisotropic_SMO);
		
		cbShowRoughnessMap.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				mesh.setMode(SELECTION_MODE.ROUGHNESS_MAP);
				mesh.setSmoothnessLevel(0);
				mesh.resetColorScale_Curvatures();
			}
		});
		
		cbShowCurvatureAvg_ORI.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				mesh.setMode(SELECTION_MODE.CURVATURE_ANISOTROPIC_AVG);
				mesh.setSmoothnessLevel(0);
				
				mesh.resetColorScale_Curvatures();
			}
		});
		cbShowCurvatureAnisotropic_ORI.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				mesh.setMode(SELECTION_MODE.CURVATURE_ANISOTROPIC);
				mesh.setSmoothnessLevel(0);
				
				mesh.resetColorScale_Curvatures();
			}
		});
		cbShowCurvatureAvg_SMO.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				mesh.setMode(SELECTION_MODE.CURVATURE_ANISOTROPIC_AVG);
				mesh.setSmoothnessLevel(mesh.getSmoothnessLevel());
				mesh.resetColorScale_Curvatures();
			}
		});
		cbShowCurvatureAnisotropic_SMO.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				mesh.setMode(SELECTION_MODE.CURVATURE_ANISOTROPIC);
				mesh.setSmoothnessLevel(mesh.getSmoothnessLevel());
				mesh.resetColorScale_Curvatures();
			}
		});
		
		JButton btnCompute = new JButton("Compute");
		btnCompute.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent arg0)
			{
				if (mesh != null)
				{
					String strScale = txtScaleParameter.getText();
					String strIter = txtSmoothingIter.getText();
					
					double scale = Double.valueOf(strScale);
					int iter = Integer.valueOf(strIter);
					
					mesh.computeRoughnessMaps(scale/100.0, iter);
//					mesh.computeRoughnessMapV2(scale/100.0, iter);
					mesh.setMode(SELECTION_MODE.ROUGHNESS_MAP);
					setSmoothnessSliderTicks(iter);
				}
				else
				{
					
				}
			}
		});
		
		c.gridx = 0;
		c.gridy = 0;
		c.anchor = GridBagConstraints.WEST;
		panel.add(new JLabel("Scale Parameter"), c);
		
		c.gridx = 1;
		c.gridy = 0;
		panel.add(txtScaleParameter, c);
		
		c.gridx = 0;
		c.gridy = 1;
		panel.add(new JLabel("Smoothing Iterations"), c);
		
		c.gridx = 1;
		c.gridy = 1;
		c.gridheight = 1;
		panel.add(txtSmoothingIter, c);
		
		c.gridx = 2;
		c.gridy = 0;
		c.gridheight = 2;
		panel.add(displayOptions, c);
		
		c.gridx = 0;
		c.gridy = 2;
		c.gridwidth = 3;
		c.anchor = GridBagConstraints.CENTER;
		panel.add(btnCompute, c);
		
		return panel;
	}

	protected JPanel createPrincipleCurvatureComputationPanel()
	{
		JPanel panel = new JPanel(new GridBagLayout());
		GridBagConstraints c = new GridBagConstraints();
		c.fill = GridBagConstraints.NONE;
		
		JPanel displayOptions = new JPanel(new GridLayout(2, 1));
		displayOptions.setBorder(BorderFactory.createTitledBorder("Display"));
		cbShowCurvatureMean = new JCheckBox("Curvature Mean");
		cbShowCurvatureGaussian = new JCheckBox("Curvature Gaussian");
		ButtonGroup curvatureGroup = new ButtonGroup();
		curvatureGroup.add(cbShowCurvatureMean);
		curvatureGroup.add(cbShowCurvatureGaussian);
		displayOptions.add(cbShowCurvatureMean);
		displayOptions.add(cbShowCurvatureGaussian);
		
		cbShowCurvatureMean.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				mesh.setMode(SELECTION_MODE.CURVATURE_MEAN);
				mesh.resetColorScale_Curvatures();
				mesh.updateColors = true;
			}
		});
		cbShowCurvatureGaussian.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				mesh.setMode(SELECTION_MODE.CURVATURE_GAUSSIAN);
				mesh.resetColorScale_Curvatures();
				mesh.updateColors = true;
			}
		});
		
		JButton btnCompute = new JButton("Compute Principle Curvatures");
		btnCompute.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent arg0)
			{
				if (mesh != null)
				{
					mesh.computePrincipleCurvatures_Taubin();
					if (cbShowCurvatureMean.isSelected()) mesh.setMode(SELECTION_MODE.CURVATURE_MEAN);
					else if (cbShowCurvatureGaussian.isSelected()) mesh.setMode(SELECTION_MODE.CURVATURE_GAUSSIAN);
					else mesh.setMode(SELECTION_MODE.NONE);
					mesh.resetColorScale_Curvatures();
				}
				else
				{
					
				}
			}
		});

		c.gridx = 0;
		c.gridy = 0;
		panel.add(displayOptions);
		
		c.gridx = 0;
		c.gridy = 1;
		c.anchor = GridBagConstraints.CENTER;
		panel.add(btnCompute);
		
		return panel;
	}
	
	protected JPanel createRingCurvatureComputationPanel()
	{
		JPanel panel = new JPanel();
		GridBagConstraints c = new GridBagConstraints();
		c.fill = GridBagConstraints.NONE;
		
		txtNRing = new JTextField("5", 5);
		txtNRing.addKeyListener(new NumericKeyAdapter());
		
		JButton btnCompute = new JButton("Compute N-Ring Curvature Map");
		btnCompute.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				int n = Integer.parseInt(txtNRing.getText());
				mesh.computeNRingCurvatureMap(n);
				mesh.setMode(SELECTION_MODE.CURVATURE_RING_MAPS);
				mesh.resetColorScale_Curvatures();				
				mesh.updateColors = true;
			}
		});
		
		c.gridx = 0;
		c.gridy = 0;
		panel.add(new JLabel("Rings: "), c);
		panel.add(txtNRing, c);
		
		c.gridx = 0;
		c.gridy = 1;
		c.anchor = GridBagConstraints.CENTER;
		panel.add(btnCompute);
		
		return panel;
	}
	
	protected JPanel createMeshOperationsPanel()
	{
		JPanel panel = new JPanel(new GridBagLayout());
		GridBagConstraints c = new GridBagConstraints();
		c.fill = GridBagConstraints.BOTH;
		
		smoothingPanel = new JPanel(new CardLayout());
		
		// first hook up the smoothing list selection
		smoothingAlgorithms = new JList(strOperationTypes);
		smoothingAlgorithms.addListSelectionListener(new ListSelectionListener() {
			
			@Override
			public void valueChanged(ListSelectionEvent e)
			{
				String type = (String) smoothingAlgorithms.getSelectedValue();
				if (type != null)
				{
					CardLayout c = (CardLayout)smoothingPanel.getLayout();
					c.show(smoothingPanel, type);
				}
			}
		});
		
		JScrollPane smoothingListPane = new JScrollPane(smoothingAlgorithms);
		smoothingAlgorithms.setSelectedIndex(0);

		// now create the individual panels for each smoothing algorithm
		JPanel laplacianPanel = new JPanel();							// Geometric Laplacian
		txtLaplacianIterations = new JTextField("10", 5);
		txtLaplacianIterations.addKeyListener(new NumericKeyAdapter());
		
		laplacianPanel.add(new JLabel("Iterations: "));
		laplacianPanel.add(txtLaplacianIterations);
		
		JPanel adaptivePanel = new JPanel(new GridBagLayout()); 		// Adaptive
		GridBagConstraints adaptive_c = new GridBagConstraints();
		adaptive_c.fill = GridBagConstraints.BOTH;
		txtAdaptiveIterations = new JTextField("5", 5);
		txtAdaptiveIterations.addKeyListener(new NumericKeyAdapter());
		
		txtAdaptiveScale = new JTextField("1", 5);
		txtAdaptiveScale.addKeyListener(new NumericKeyAdapter());
		
		adaptive_c.gridx = 0;
		adaptive_c.gridy = 0;
		adaptive_c.anchor = GridBagConstraints.WEST;
		adaptivePanel.add(new JLabel("Iterations: "), adaptive_c);
		
		adaptive_c.gridx = 1;
		adaptive_c.gridy = 0;
		adaptivePanel.add(txtAdaptiveIterations, adaptive_c);

		adaptive_c.gridx = 0;
		adaptive_c.gridy = 1;
		adaptivePanel.add(new JLabel("Scale Parameter: "), adaptive_c);
		
		adaptive_c.gridx = 1;
		adaptive_c.gridy = 1;
		adaptivePanel.add(txtAdaptiveScale, adaptive_c);
		
		JPanel taubinPanel = new JPanel();							// Taubin Smoothing
		txtTaubinIterations = new JTextField("5", 5);
		txtTaubinIterations.addKeyListener(new NumericKeyAdapter());
		
		taubinPanel.add(new JLabel("Iterations: "));
		taubinPanel.add(txtTaubinIterations);
		
		smoothingPanel.add(laplacianPanel, strOperationTypes[0]);
		smoothingPanel.add(adaptivePanel, strOperationTypes[1]);
		smoothingPanel.add(taubinPanel, strOperationTypes[2]);
		
		JPanel controlPanel = new JPanel(new GridBagLayout());
		GridBagConstraints ctrl_constraints = new GridBagConstraints();
		ctrl_constraints.fill = GridBagConstraints.BOTH;
		sliderSmoothnessLevel = new JSlider(0, 0, 0);
		sliderSmoothnessLevel.setPaintLabels(true);
		sliderSmoothnessLevel.setPaintTicks(true);
		sliderSmoothnessLevel.addChangeListener(new ChangeListener()
		{
			public void stateChanged(ChangeEvent e)
			{
				if (sliderSmoothnessLevel.getValueIsAdjusting() == false)
				{
					int level = sliderSmoothnessLevel.getValue();
					mesh.setSmoothnessLevel(level);
				}
			}
		});
		
		ctrl_constraints.gridx = 0;
		ctrl_constraints.gridy = 0;
		ctrl_constraints.anchor = GridBagConstraints.WEST;
		controlPanel.add(new JLabel("Level of Smoothness: "), ctrl_constraints);
		
		ctrl_constraints.gridx = 1;
		ctrl_constraints.gridy = 0;
		ctrl_constraints.weightx = 0.75;
		controlPanel.add(sliderSmoothnessLevel, ctrl_constraints);
		
		JButton btnPerformSmoothing = new JButton("Perform Smoothing");
		btnPerformSmoothing.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent e)
			{
				String type = (String) smoothingAlgorithms.getSelectedValue();
				if (type != null)
				{
					if (type.equals(strOperationTypes[0])) // laplacian
					{
						int iter = Integer.parseInt(txtLaplacianIterations.getText());
						mesh.smoothMesh_geometricLaplacian(iter);
						setSmoothnessSliderTicks(iter);
					}
					else if (type.equals(strOperationTypes[1])) // adaptive
					{
						int iter = Integer.parseInt(txtAdaptiveIterations.getText());
						double scale = Double.parseDouble(txtAdaptiveScale.getText()); // percent of the longest length of bounding box
						mesh.smoothMesh_Adaptive(iter, scale/100.0);
						setSmoothnessSliderTicks(iter);
					}
					else if (type.equals(strOperationTypes[2])) // taubin
					{
						int iter = Integer.parseInt(txtTaubinIterations.getText());
						mesh.smoothMesh_Taubin(iter);
						setSmoothnessSliderTicks(iter);
					}
				}
			}
		});
		
		JPanel wrapperSmoothingPanel = new JPanel(new BorderLayout());
		wrapperSmoothingPanel.add(controlPanel, BorderLayout.NORTH);
		wrapperSmoothingPanel.add(smoothingPanel, BorderLayout.CENTER);
		wrapperSmoothingPanel.add(btnPerformSmoothing, BorderLayout.SOUTH);
		
		
		c.gridx = 0;
		c.gridy = 0;
		c.weightx = 0.25;
		c.anchor = GridBagConstraints.WEST;
		panel.add(smoothingListPane, c);
		
		c.gridx = 1;
		c.gridy = 0;
		c.weightx = 0.75;
		c.anchor = GridBagConstraints.EAST;
		panel.add(wrapperSmoothingPanel, c);
		
		
		
		return panel;
	}

	// GENERAL OPTIONS (eg: lights, show triangulation as 2D graph)
	protected JPanel createGeneralOptionsPanel()
	{
		JPanel panel = new JPanel(new GridBagLayout());
		GridBagConstraints c = new GridBagConstraints();
		c.fill = GridBagConstraints.NONE;
		
		JPanel lightsPanel = new JPanel();
		lightsPanel.setBorder(BorderFactory.createTitledBorder("Lights"));

		cbLightSwitch = new JCheckBox("Lights");
		cbLightSwitch.setSelected(false);
		cbLightSwitch.addItemListener(new ItemListener()
		{
			public void itemStateChanged(ItemEvent e)
			{
				if (cbLightSwitch.isSelected()) mesh.enableLights();
				else mesh.disableLights();
			}
		});
		
		cbLightsPerTriangle = new JCheckBox("Per Triangle");
		cbLightsPerTriangle.setSelected(false);
		cbLightsPerTriangle.addItemListener(new ItemListener()
		{
			public void itemStateChanged(ItemEvent e)
			{
				if (cbLightsPerTriangle.isSelected()) mesh.usePerTriangleNormal = true;
				else mesh.usePerTriangleNormal = false;
			}
		});
		
		cbLightsPerVertex = new JCheckBox("Per Vertex");
		cbLightsPerVertex.setSelected(false);
		cbLightsPerVertex.addItemListener(new ItemListener()
		{
			public void itemStateChanged(ItemEvent e)
			{
				if (cbLightsPerVertex.isSelected()) mesh.usePerVertexNormal = true;
				else mesh.usePerVertexNormal = false;
			}
		});
		
		ButtonGroup lightsGroup = new ButtonGroup();
		lightsGroup.add(cbLightsPerTriangle);
		lightsGroup.add(cbLightsPerVertex);
		lightsPanel.add(cbLightSwitch);
		lightsPanel.add(cbLightsPerTriangle);
		lightsPanel.add(cbLightsPerVertex);
		
		
		// options for rendering types
		JPanel renderTypes = new JPanel(new GridLayout(3, 1));
		renderTypes.setBorder(BorderFactory.createTitledBorder("Display"));
		ButtonGroup renderGroup = new ButtonGroup();
		rRenderPoints = new JRadioButton("Points");
		rRenderLines = new JRadioButton("Wireframe");
		rRenderPolygons = new JRadioButton("Polygons", true);
		
		renderGroup.add(rRenderPoints);
		renderGroup.add(rRenderLines);
		renderGroup.add(rRenderPolygons);
		
		rRenderPoints.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { mesh.setRenderFormat_Points(); } });
		rRenderLines.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { mesh.setRenderFormat_Wireframe(); } });
		rRenderPolygons.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { mesh.setRenderFormat_Polygon(); } });
		
		renderTypes.add(rRenderPoints);
		renderTypes.add(rRenderLines);
		renderTypes.add(rRenderPolygons);
		
		btnShowTriangulation = new JButton("Overhead Projection");
		btnShowTriangulation.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				Triangulation2DFrame frame = new Triangulation2DFrame("Top Down View", mesh);
				frame.setVisible(true);
			}
		});
		
		btnShowStrikeDip2D = new JButton("Strike Dip 2D");
		btnShowStrikeDip2D.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				StrikeDip2DFrame frame = new StrikeDip2DFrame("Strike Dip 2D", mesh);
				frame.setVisible(true);
			}
		});
		
		
//		JPanel meshScalePanel = new JPanel();
//		txtMeshScale = new JTextField("1.0", 5);
//		txtMeshScale.addKeyListener(new NumericKeyAdapter());
//		txtMeshScale.addActionListener(new ActionListener()
//		{
//			public void actionPerformed(ActionEvent e)
//			{
//				String str = txtMeshScale.getText();
//				float scale = Float.parseFloat(str);
//				mesh.setMeshScale(scale);
//			}
//		});
//
//		meshScalePanel.add(new JLabel("Mesh Scale (%):"));
//		meshScalePanel.add(txtMeshScale);
		
		cbDisplayNormals = new JCheckBox("Display Normals", true);
		cbDisplayNormals.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { mesh.showNormals = !mesh.showNormals; } });
		
//		// TODO: add back face culling options
//		cbBackFaceCulling = new JCheckBox("Back Face Culling");
//		cbBackFaceCulling.setEnabled(true);
//		cbBackFaceCulling.addActionListener(new ActionListener()
//		{
//			public void actionPerformed(ActionEvent e)
//			{
//				if (cbBackFaceCulling.isEnabled())
//				{
//					mesh.enableBackFaceCulling();
//				}
//				else
//				{
//					mesh.disableBackFaceCulling();
//				}
//			}
//		});
//		
		
		c.gridx = 0;
		c.gridy = 0;
		c.anchor = GridBagConstraints.WEST;
//		panel.add(lightsPanel, c); // enable for per vertex and per triangle lighting
		panel.add(cbLightSwitch, c);
		
		c.gridx = 0;
		c.gridy = 1;
		panel.add(renderTypes, c);

		
		c.gridx = 1;
		c.gridy = 0;
		c.anchor = GridBagConstraints.CENTER;
//		panel.add(meshScalePanel, c);
		panel.add(cbDisplayNormals, c);
		
		JPanel btnPanel = new JPanel();
		btnPanel.add(btnShowTriangulation);
		btnPanel.add(btnShowStrikeDip2D);
		c.gridx = 1;
		c.gridy = 1;
		panel.add(btnPanel, c);
		
		c.gridx = 2;
		c.gridy = 0;
		panel.add(createColorScalingPanel(), c);
		
		return panel;
	}
	
	protected JPanel createColorScalingPanel()
	{
		JPanel panel = new JPanel();
		panel.setBorder(BorderFactory.createTitledBorder("Color Scaling"));
		
		cbColorScale_NONE = new JCheckBox("None", true);
		cbColorScale_LOG = new JCheckBox("Log");
		cbColorScale_SQRT = new JCheckBox("Sqrt");
		
		ButtonGroup group = new ButtonGroup();
		group.add(cbColorScale_NONE);
		group.add(cbColorScale_LOG);
		group.add(cbColorScale_SQRT);
		
		cbColorScale_NONE.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				mesh.colorScaleMode = SCALING_MODE.NONE;
				mesh.updateComplete = true;
			}
		});
		
		cbColorScale_LOG.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				mesh.colorScaleMode = SCALING_MODE.LOG;
				mesh.updateComplete = true;
			}
		});
		
		cbColorScale_SQRT.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				mesh.colorScaleMode = SCALING_MODE.SQRT;
				mesh.updateComplete = true;
			}
		});
		
		panel.add(cbColorScale_NONE);
		panel.add(cbColorScale_LOG);
		panel.add(cbColorScale_SQRT);
		
		return panel;
	}
	
	public void setMesh(SimpleTriangulation m) { this.mesh = m; }
	
	// helper functions
	private void setSmoothnessSliderTicks(int iter)
	{
		sliderSmoothnessLevel.setMinimum(0);
		sliderSmoothnessLevel.setMaximum(iter);
		sliderSmoothnessLevel.setMajorTickSpacing(iter/4); // have some visual ticks at 25% intervals
	}
}
